package com.leyou.search.service;

import com.fasterxml.jackson.core.type.TypeReference;
import com.leyou.common.domain.PageResult;
import com.leyou.common.exception.domain.ExceptionEnum;
import com.leyou.common.exception.domain.LyException;
import com.leyou.common.utils.BeanHelper;
import com.leyou.common.utils.JsonUtils;
import com.leyou.item.client.ItemClient;
import com.leyou.item.dto.*;
import com.leyou.search.domain.Goods;
import com.leyou.search.dto.GoodsDTO;
import com.leyou.search.dto.SearchRequest;
import com.leyou.search.repository.SearchRepository;
import com.leyou.search.utils.HighlightUtils;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

@Service
public class SearchService {

    @Autowired
    private ItemClient itemClient;

    @Autowired
    private ElasticsearchTemplate esTemplate;

    @Autowired
    private SearchRepository searchRepository;

    /**
     * 编写将SpuDTO转换成Goods对象的方法
     */
    public Goods buildGoods(SpuDTO spuDTO){
        //根据spuId查询出sku的集合
        List<SkuDTO> skuDTOS = itemClient.findSkusBySpuId(spuDTO.getId());
        //new一个list集合里面放置map，来存储sku中所需要的信息
        List<Map<String, Object>> skuList = new ArrayList<>();
        //遍历skuDTOS
        skuDTOS.forEach(skuDTO -> {
            //new一个map
            Map<String, Object> skuMap = new HashMap<>();
            skuMap.put("id", skuDTO.getId());
            skuMap.put("title", skuDTO.getTitle().substring(spuDTO.getName().length()));
            skuMap.put("price", skuDTO.getPrice());
            skuMap.put("image", StringUtils.substringBefore(skuDTO.getImages(), ","));
            //把skuMap添加到skuList中
            skuList.add(skuMap);
        });

        //收集所有sku对象的价格
        Set<Long> priceSet = skuDTOS.stream().map(SkuDTO::getPrice).collect(Collectors.toSet());

        //创建一个map存储规格参数过滤项
        Map<String, Object> specsMap = new HashMap<>();
        //根据当前spu的分类查询规格参数，只要可以被搜索的
        List<SpecParamDTO> specsKeys = itemClient.findSpecParams(null, spuDTO.getCid3(), true);
        //根据spuid得到SpuDetail对象
        SpuDetailDTO spuDetailDTO = itemClient.findSpuDetailDTObyId(spuDTO.getId());
        //得到通用规格参数
        String genericSpecStr = spuDetailDTO.getGenericSpec();
        //将通用规格参数字符串结构转成map结构
        Map<Long, String> genericSpecMap = JsonUtils.toMap(genericSpecStr, Long.class, String.class);
        //得到特有规格参数
        String specialSpecStr = spuDetailDTO.getSpecialSpec();
        //将特有规格参数字符串结构转成map结构
        Map<Long, List<String>> specialSpecMap = JsonUtils.nativeRead(specialSpecStr, new TypeReference<Map<Long, List<String>>>() {});
        //注意：这里规格参数map的长度等于当前规格参数数组的长度
        specsKeys.forEach(param->{
            //获取规格参数的名称
            String key = param.getName();
            //获取规格参数的值
            Object value = null;
            //判断规格参数值的来源
            if(param.getGeneric()){
                value = genericSpecMap.get(param.getId());
            }else {
                value = specialSpecMap.get(param.getId());
            }
            //把规格参数中所有的数字类型替换为区间
            if(param.getNumeric()){
                value = chooseSegment(value, param);
            }
            //给规格过滤项的map赋值
            specsMap.put(key, value);
        });

        //创建出Goods对象
        Goods goods = new Goods();
        goods.setId(spuDTO.getId());
        goods.setTitle(spuDTO.getName());
        goods.setSubTitle(spuDTO.getSubTitle());
        goods.setBrandId(spuDTO.getBrandId());
        goods.setCategoryId(spuDTO.getCid3());
        goods.setCreateTime(spuDTO.getCreateTime().getTime());
        goods.setAll(spuDTO.getName()+spuDTO.getBrandName()+spuDTO.getCategoryName());
        goods.setSkus(JsonUtils.toString(skuList));
        goods.setPrice(priceSet);
        goods.setSpecs(specsMap);
        return goods;
    }

    /**
     * 不用自己写，直接复制即可
     * @param value 是规格参数中的数值类型的值
     * @param p 当前的规格参数对象
     * @return
     */
    private String chooseSegment(Object value, SpecParamDTO p) {
        if (value == null || StringUtils.isBlank(value.toString())) {
            return "其它";
        }
        double val = parseDouble(value.toString());
        String result = "其它";
        // 保存数值段
        for (String segment : p.getSegments().split(",")) {
            String[] segs = segment.split("-");
            // 获取数值范围
            double begin = parseDouble(segs[0]);
            double end = Double.MAX_VALUE;
            if (segs.length == 2) {
                end = parseDouble(segs[1]);
            }
            // 判断是否在范围内
            if (val >= begin && val < end) {
                if (segs.length == 1) {
                    result = segs[0] + p.getUnit() + "以上";
                } else if (begin == 0) {
                    result = segs[1] + p.getUnit() + "以下";
                } else {
                    result = segment + p.getUnit();
                }
                break;
            }
        }
        return result;
    }

    private double parseDouble(String str) {
        try {
            return Double.parseDouble(str);
        } catch (Exception e) {
            return 0;
        }
    }

    /**
     * 加载搜索页面的商品列表
     * @param request
     * @return
     */
    public PageResult<GoodsDTO> loadGoodsList(SearchRequest request) {
        try {
            //提供一个封装复杂条件的对象
            NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
            //封装分页信息
            nativeSearchQueryBuilder.withPageable(PageRequest.of(request.getPage()-1, request.getSize()));
            //封装要查询的字段
            nativeSearchQueryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{"id","title","subTitle","skus"}, null));
            //封装查询条件
            nativeSearchQueryBuilder.withQuery(buildQueryParams(request));

            //设置高亮头
            HighlightUtils.highlightField(nativeSearchQueryBuilder, "title");
            //执行索引库的复杂查询
            AggregatedPage<Goods> aggregatedPage = esTemplate.queryForPage(nativeSearchQueryBuilder.build(), Goods.class,
                    HighlightUtils.highlightBody(Goods.class, "title"));

            //获取索引库中的数据列表
            List<Goods> content = aggregatedPage.getContent();
            //把Goods列表转成GoodsDTO
            List<GoodsDTO> items = BeanHelper.copyWithCollection(content, GoodsDTO.class);
            //创建一个自己的分页对象
            PageResult<GoodsDTO> pageResult = new PageResult<>(aggregatedPage.getTotalElements(), aggregatedPage.getTotalPages(), items);
            return pageResult;
        }catch (Exception e){
            throw new LyException(ExceptionEnum.GOODS_NOT_FOUND);
        }

    }

    /**
     * 封装查询的条件
     * @param request
     * @return
     */
    private QueryBuilder buildQueryParams(SearchRequest request) {
        //创建一个bool查询的对象
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        //在bool查询中封装搜索查询
        //.operator(Operator.AND)表示满足所有分词关键字才能匹配到
        boolQuery.must(QueryBuilders.multiMatchQuery(request.getKey(), "all", "title").operator(Operator.AND));

        //获取到过滤条件的map
        Map<String, Object> specParamMap = request.getSpecParamMap();
        if(specParamMap!=null && specParamMap.size()!=0){
            //遍历
            specParamMap.entrySet().forEach(entry->{
                //得到页面传过来的过滤条件的key
                String key = entry.getKey();
                //将这些key值转成与索引库相同的域字段名称
                if(StringUtils.equals(key, "分类")){
                    key = "categoryId";
                }else if(StringUtils.equals(key, "品牌")){
                    key = "brandId";
                }else {
                    key = "specs."+key+".keyword";
                }
                //获取过滤条件数据
                Object filterParam = entry.getValue();
                //在bool查询中封装过滤查询
                boolQuery.filter(QueryBuilders.termQuery(key, filterParam));
            });
        }
        return boolQuery;
    }

    /**
     * 加载搜索页面过滤条件列表
     * @param request
     * @return
     */
    public Map<String, List<?>> loadFilterParamsList(SearchRequest request) {
        //创建一个存储过滤参数的map
        Map<String, List<?>> filterParamsMap = new LinkedHashMap<>();
        //得到封装条件的对象
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
        //封装分页信息
        nativeSearchQueryBuilder.withPageable(PageRequest.of(0, 1));
        //封装查询条件
        nativeSearchQueryBuilder.withQuery(buildQueryParams(request));

        //给分类聚合起个名字
        String categoryName = "categoryName";
        //添加分类聚合字段
        nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(categoryName).field("categoryId"));

        //给品牌聚合起个名字
        String brandName = "brandName";
        //添加分类聚合字段
        nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(brandName).field("brandId"));

        //聚合查询
        AggregatedPage<Goods> goodsAggregated = esTemplate.queryForPage(nativeSearchQueryBuilder.build(), Goods.class);

        //得到所有的聚合结果
        Aggregations aggregations = goodsAggregated.getAggregations();

        //解析分类聚合结果
        Terms categoryTerms = aggregations.get(categoryName);
        //将分类的聚合结果放入到filterParamsMap里面
        List<Long> categoryIds = handlerCategoryTerms(filterParamsMap, categoryTerms);

        //解析品牌聚合结果
        Terms brandTerms = aggregations.get(brandName);
        //将品牌的聚合结果放入到filterParamsMap里面
        handlerBrandTerms(filterParamsMap, brandTerms);

        //封装动态过滤参数
        handlerSpecFilterMap(filterParamsMap, categoryIds, buildQueryParams(request));

        return filterParamsMap;
    }

    /**
     * 编写封装动态规格过滤参数的方法
     */
    private void handlerSpecFilterMap(Map<String, List<?>> filterParamsMap, List<Long> categoryIds, QueryBuilder buildQueryParams) {
        categoryIds.forEach(categoryId->{
            //得到封装条件的对象
            NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
            //封装分页信息
            nativeSearchQueryBuilder.withPageable(PageRequest.of(0, 1));
            //封装查询条件
            nativeSearchQueryBuilder.withQuery(buildQueryParams);

            //根据分类id查询出当前分类下的可搜索的规格参数
            List<SpecParamDTO> specParams = itemClient.findSpecParams(null, categoryId, true);

            //遍历规格参数集合，对每一个规格项进行聚合
            specParams.forEach(specParamDTO -> {
                //给当前规格参数聚合起个名字
                String specParamName = specParamDTO.getName();
                //指定要聚合的字段域，该字段必须是索引库中域的名称
                String specParamField = "specs."+specParamDTO.getName()+".keyword";
                //添加分类聚合字段
                nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(specParamName).field(specParamField));
            });

            //聚合查询
            AggregatedPage<Goods> goodsAggregated = esTemplate.queryForPage(nativeSearchQueryBuilder.build(), Goods.class);

            //得到所有的聚合结果
            Aggregations aggregations = goodsAggregated.getAggregations();

            //遍历聚合结果并解析
            specParams.forEach(specParamDTO -> {
                //给当前规格参数聚合起个名字
                String specParamName = specParamDTO.getName();
                //解析每个规格参数的聚合结果
                Terms specTerms = aggregations.get(specParamName);
                //得到当前规格参数聚合桶中所有结果的集合
                List<String> specParamList = specTerms.getBuckets().stream().map(Terms.Bucket::getKeyAsString).collect(Collectors.toList());
                //把当前规格参数聚合结果放入到结果map中
                filterParamsMap.put(specParamName, specParamList);
            });
        });
    }

    /**
     * 将品牌的聚合结果放入到filterParamsMap里面
     */
    private void handlerBrandTerms(Map<String, List<?>> filterParamsMap, Terms brandTerms) {
        List<BrandDTO> brandDTOS = brandTerms.getBuckets().stream()//将品牌结果桶的集合转成流
                .map(Terms.Bucket::getKeyAsNumber)//得到每一个桶中的Number类型的key
                .map(Number::longValue)//将Number类型的key转成Long
                .map(itemClient::findBrandById)//没一个Long类型的id都执行itemClient接口中的findBrandById得到brand对象
                .collect(Collectors.toList());//把所有Brand对象收集为集合
        filterParamsMap.put("品牌", brandDTOS);
    }

    /**
     * 将分类的聚合结果放入到filterParamsMap里面
     */
    private List<Long> handlerCategoryTerms(Map<String, List<?>> filterParamsMap, Terms categoryTerms) {
        List<Long> categoryIdList = categoryTerms.getBuckets().stream()//将分类结果桶的集合转成流
                .map(Terms.Bucket::getKeyAsNumber)//得到每一个桶中的Number类型的key
                .map(Number::longValue)//将Number类型的key转成Long
                .collect(Collectors.toList());//将Long类型的key收集为List集合
        //根据分类id的集合查询分类对象的集合
        List<CategoryDTO> categoryDTOS = itemClient.findCategorysByCids(categoryIdList);
        filterParamsMap.put("分类", categoryDTOS);
        return categoryIdList;
    }

    public void addIndex(Long id) {
        SpuDTO spu = itemClient.findSpuById(id);
        Goods goods = buildGoods(spu);
        searchRepository.save(goods);
    }

    public void delIndex(Long id) {
        searchRepository.deleteById(id);
    }
}
